App.require(["matrix4", "quaternion", "vector3"], function (matrix4, quaternion, vector3) {
    var Matrix4 = matrix4.Matrix4, Quaternion = quaternion.Quaternion, Vector3 = vector3.Vector3;

    function Euler(x, y, z, order) {
        this.x = x || 0;
        this.y = y || 0;
        this.z = z || 0;
        this.order = order || Euler.DefaultOrder;
    }

    Euler.RotationOrders = ['XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX'];
    Euler.DefaultOrder = 'XYZ';

    Euler.prototype = {
        constructor: Euler,
        set: function (x, y, z, order) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.order = order || this.order;

            return this;
        },

        clone: function () {
            return new this.constructor(this.x, this.y, this.z, this.order);
        },

        copy: function (euler) {
            this.x = euler.x;
            this.y = euler.y;
            this.z = euler.z;
            this.order = euler.order;

            return this;
        },

        reorder: function (newOrder) {
            Euler.quaternion = Euler.quaternion || new Quaternion();
            Euler.quaternion.setFromEuler(this);
            return this.setFromQuaternion(Euler.quaternion, newOrder);
        },

        equals: function (euler) {
            return (euler.x === this.x) && (euler.y === this.y) && (euler.z === this.z) && (euler.order === this.order);
        },

        toArray: function (array, offset) {
            if (array === undefined) array = [];
            if (offset === undefined) offset = 0;

            array[offset] = this.x;
            array[offset + 1] = this.y;
            array[offset + 2] = this.z;
            array[offset + 3] = this.order;

            return array;
        },

        toVector3: function (optionalResult) {
            if (optionalResult) {
                return optionalResult.set(this.x, this.y, this.z);
            } else {
                return new Vector3(this.x, this.y, this.z);
            }
        }
    };

    Euler.fromRotationMatrix = function (m, order, e) {
        e = e || new Euler();

        var clamp = _Math.clamp;
        var te = m.elements;
        var m11 = te[0],
            m12 = te[4],
            m13 = te[8];
        var m21 = te[1],
            m22 = te[5],
            m23 = te[9];
        var m31 = te[2],
            m32 = te[6],
            m33 = te[10];

        order = order || e.order;

        if (order === 'XYZ') {
            e.y = Math.asin(clamp(m13, -1, 1));
            if (Math.abs(m13) < 0.99999) {
                e.x = Math.atan2(-m23, m33);
                e.z = Math.atan2(-m12, m11);
            } else {
                e.x = Math.atan2(m32, m22);
                e.z = 0;
            }
        } else if (order === 'YXZ') {
            e.x = Math.asin(-clamp(m23, -1, 1));
            if (Math.abs(m23) < 0.99999) {
                e.y = Math.atan2(m13, m33);
                e.z = Math.atan2(m21, m22);
            } else {
                e.y = Math.atan2(-m31, m11);
                e.z = 0;
            }

        } else if (order === 'ZXY') {
            e.x = Math.asin(clamp(m32, -1, 1));
            if (Math.abs(m32) < 0.99999) {
                e.y = Math.atan2(-m31, m33);
                e.z = Math.atan2(-m12, m22);
            } else {
                e.y = 0;
                e.z = Math.atan2(m21, m11);
            }
        } else if (order === 'ZYX') {
            e.y = Math.asin(-clamp(m31, -1, 1));
            if (Math.abs(m31) < 0.99999) {
                e.x = Math.atan2(m32, m33);
                e.z = Math.atan2(m21, m11);
            } else {
                e.x = 0;
                e.z = Math.atan2(-m12, m22);
            }
        } else if (order === 'YZX') {
            e.z = Math.asin(clamp(m21, -1, 1));
            if (Math.abs(m21) < 0.99999) {
                e.x = Math.atan2(-m23, m22);
                e.y = Math.atan2(-m31, m11);
            } else {
                e.x = 0;
                e.y = Math.atan2(m13, m33);
            }
        } else {
            e.z = Math.asin(-clamp(m12, -1, 1));
            if (Math.abs(m12) < 0.99999) {
                e.x = Math.atan2(m32, m22);
                e.y = Math.atan2(m13, m11);
            } else {
                e.x = Math.atan2(-m23, m33);
                e.y = 0;
            }
        }

        e.order = order;
        return e;
    };

    Euler.fromArray = function (array, e) {
        e = e || new Euler();
        e.x = array[0];
        e.y = array[1];
        e.z = array[2];

        if (array[3] !== undefined) e.order = array[3];

        return e;
    };

    Euler.fromQuaternion = function (q, order, e) {
        e = e || new Euler();
        Euler.matrix = Euler.matrix || new Matrix4();
        Matrix4.fromQuaternion(q, Euler.matrix);
        return Euler.fromRotationMatrix(Euler.matrix, order, e);
    };

    Euler.fromVector3 = function (v, order, e) {
        e = e || new Euler();
        e.set(v.x, v.y, v.z, order || e.order);
        return e;
    };

    App.define("euler", {
        Euler: Euler
    })
})